﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Debugger.Interop;
using Red.Scripts.Debugger.Defines;

namespace Red.Scripts.Debugger.Remote
{
    [Guid( Defines.CLSID.Raw.PortSupplier )]
    public class PortSupplier : IDebugPortSupplier2, IDebugPortSupplierDescription2, IDebugPortSupplierEx2
    {
        #region Private Data

        private List<Port> _ports = new List<Port>();
        private IDebugCoreServer2 _debugCoreServer;

        #endregion Private Data

        public PortSupplier()
        {
            Uri uri = new Uri( "tcp://127.0.0.1", UriKind.Absolute );
            Port port = new Port( this, null, uri );

            _ports.Add( port );
        }

        private bool IsIpAddress( string name )
        {
            IPAddress address;
            if ( IPAddress.TryParse( name, out address ) )
                return true;

            return false;
        }

        private string SanitizeTcp( string name )
        {
            const string tcpPrefix = "tcp://";

            if ( !name.StartsWith( tcpPrefix ) )
                name = $"{tcpPrefix}{name}";

            if ( !IsIpAddress( name.Substring( tcpPrefix.Length ) ) )
                return null;

            if ( !Uri.IsWellFormedUriString( name, UriKind.Absolute ) )
                return null;

            return name;
        }

        private string SanitizeName( string name )
        {
            if ( name != null )
            {
                string tcp = SanitizeTcp( name );

                if ( tcp != null )
                {
                    return tcp;
                }
            }

            return null;
        }

        #region IDebugPortSupplier2

        public int AddPort( IDebugPortRequest2 pRequest, out IDebugPort2 ppPort )
        {
            string name;
            pRequest.GetPortName( out name );
            Debug.Print( $"Requested port name: {name}" );

            name = SanitizeName( name );

            if ( name == null )
            {
                ppPort = _ports[ 0 ];
                return Constants.S_OK;
            }

            try
            {
                Uri uri = new Uri( name, UriKind.Absolute );

                ppPort = new Port( this, pRequest, uri ) { Server = _debugCoreServer };
                return Constants.S_OK;
            }
            catch ( UriFormatException ex )
            {
                Debug.Print( $"Invalid Target uri ('{name}') specified for Red scripts\n{ex}" );
            }

            ppPort = null;
            return Constants.E_FAIL;
        }

        public int CanAddPort()
        {
            return Constants.S_OK;
        }

        public int EnumPorts( out IEnumDebugPorts2 ppEnum )
        {
            ppEnum = new Defines.Enumerators.Port( _ports.ToArray() );
            return Constants.S_OK;
        }

        public int GetPort( ref Guid guidPort, out IDebugPort2 ppPort )
        {
            foreach ( Port port in _ports )
            {
                if( port.Guid == guidPort )
                {
                    ppPort = port;
                    return Constants.S_OK;
                }
            }

            ppPort = null;
            return Constants.S_FALSE;
        }

        public int GetPortSupplierId( out Guid pguidPortSupplier )
        {
            pguidPortSupplier = Defines.Id.Guid.PortSupplier;

            return Constants.S_OK;
        }

        public int GetPortSupplierName( out string pbstrName )
        {
            pbstrName = "Red Scripts";

            return Constants.S_OK;
        }

        public int RemovePort( IDebugPort2 pPort )
        {
            return Constants.S_OK;
        }

        #endregion IDebugPortSupplier2

        #region IDebugPortSupplierDescription2

        int IDebugPortSupplierDescription2.GetDescription( enum_PORT_SUPPLIER_DESCRIPTION_FLAGS[] pdwFlags, out string pbstrText )
        {
            pbstrText = "The Red Scripts Transport allows you to debug Red Scripts on any running red engine process. Simply enter the ip address of the target machine into the \"Qualifier\" field. If the field is blank, the ip address will default to this pc (localhost) if you click refresh.";

            return Constants.S_OK;
        }

        #endregion IDebugPortSupplierDescription2

        #region IDebugPortSupplierEx2

        public int SetServer( IDebugCoreServer2 pServer )
        {
            _debugCoreServer = pServer;

            foreach( Port port in _ports )
            {
                port.Server = _debugCoreServer;
            }

            return Constants.S_OK;
        }

        #endregion IDebugPortSupplierEx2
    }
}
